#include "../src/ordinal.hh"

#include <cassert>
#include <iostream>

namespace
{
using namespace playerpiano;

struct LogPRatingTestDatum 
{ 
  struct
    {
      unsigned int      rating;
      unsigned int      true_label;
      float             loglambda;
      float             logbeta;
      float             tau[3];
    }                                   inputs;
  struct
    {
      float             logp;
      float             dlogbeta;
      float             ddlogbeta;
      float             dtau[3];
    }                                   outputs;
};

void
test_logprating (void)
{
  LogPRatingTestDatum data[] = 
    {{{0, 3, 0.155129,  0.012101, {0.0682151, 0.836322,  0.452838}}, {-4.59086, -3.50403, -3.72774, {1.17004, 0.957618,  0.492151}}}, {{2, 3, 0.77722,  0.194512, {0.964409, 0.0227534, 0.754836}}, {-0.425584,  0.277639, -0.0116264, {-0.000601953, -0.0354239, 0.880498}}}, {{1,  0, 0.156297,  0.339076, {0.412443, 0.710343,  0.901124}}, {-1.70417, -1.20002, -1.64861, {-1.33459, 0.00796697,  0.0000298949}}}, {{0, 3, 0.322206,  0.0243031, {0.99918, 0.0249958,  0.279128}}, {-5.12675, -4.25016, -4.62238, {1.40573, 1.33563,  0.771268}}}, {{3, 2, 0.912977,  0.804005, {0.57512, 0.614532,  0.757848}}, {-8.0694, -7.39955, -7.5998, {-0.0210496, -3.64867,  -5.56596}}}, {{1, 0, 0.791904,  0.506905, {0.778209, 0.305009,  0.0146846}}, {-4.69375, -4.64153, -4.8421, {-3.63134,  0.0000449445, 4.46809e-9}}}, {{2, 2, 0.312392,  0.813801, {0.282256, 0.259849, 0.156095}}, {-0.482547,  0.414023, -0.0492135, {-0.0212299, -0.928828, 0.251662}}}, {{1, 1,  0.474725,  0.869813, {0.549506, 0.254971,  0.152519}}, {-0.817345, -0.030384, -0.256972, {-2.04854,  0.0937214, 0.000165103}}}, {{1, 0, 0.84551,  0.550326, {0.229976, 0.873392,  0.932533}}, {-2.99902, -2.80094, -3.21296, {-3.83708,  0.0000138426, 1.32171e-11}}}, {{3, 3, 0.746321,  0.654856, {0.25886, 0.174685,  0.954417}}, {-1.99562, -1.57282, -2.07621, {-1.79733e-6,  -0.0160808, -3.50811}}}};

  for (unsigned int i = 0; i < sizeof (data) / sizeof (data[0]); ++i)
    {
      Ordinal ordinal (data[i].inputs.tau, 0, &data[i].inputs.loglambda, 0, 1, 1, 4, 0);
      Rating rating = { 0, data[i].inputs.rating };
      float dlogbeta = drand48 ();
      float ddlogbeta = drand48 ();

      float logp = ordinal.logprating (rating, 
                                       data[i].inputs.true_label,
                                       exp (data[i].inputs.logbeta),
                                       dlogbeta,
                                       ddlogbeta);

      assert (fabs (logp - data[i].outputs.logp) <=
              1e-4 * (1 + fabs (logp) + fabs (data[i].outputs.logp)) ||
              (std::cerr << logp << " ?= " << data[i].outputs.logp << std::endl,
               0));

      assert (fabs (dlogbeta - data[i].outputs.dlogbeta) <=
              1e-4 * (1 + fabs (dlogbeta) + fabs (data[i].outputs.dlogbeta)) ||
              (std::cerr << dlogbeta << " ?= " << data[i].outputs.dlogbeta << std::endl,
               0));

      assert (fabs (ddlogbeta - data[i].outputs.ddlogbeta) <=
              1e-4 * (1 + fabs (ddlogbeta) + fabs (data[i].outputs.ddlogbeta)) ||
              (std::cerr << ddlogbeta << " ?= " << data[i].outputs.ddlogbeta << std::endl,
               0));

      std::vector<float> pz (4, 0);
      pz[data[i].inputs.true_label] = 1;

      Ordinal::DFunc dfunc (rating,
                            pz,
                            exp (data[i].inputs.logbeta),
                            data[i].inputs.tau,
                            data[i].inputs.loglambda,
                            4);

      PianoVec s0 (4, 0);
      PianoVec sprime0 (dfunc (0, s0));

      assert (fabs (sprime0[0] - data[i].outputs.dlogbeta) <=
              1e-4 * (1 + fabs (sprime0[0]) + fabs (data[i].outputs.dlogbeta)) ||
              (std::cerr << sprime0[0] << " ?= " << data[i].outputs.dlogbeta << std::endl,
               0));

      for (unsigned int j = 0; j < 3; ++j)
        {
          assert (fabs (sprime0[j+1] - data[i].outputs.dtau[j]) <=
                  1e-4 * (1 + fabs (sprime0[j+1]) + fabs (data[i].outputs.dtau[j])) ||
                  (std::cerr << "dtau[" << j << "] = " << sprime0[j+1] << " ?= " << data[i].outputs.dtau[j] << std::endl,
                   0));
        }

    }
}

struct GradTestDatum 
{ 
  struct
    {
      float             loglambda[4];
      float             tau[4 * 4];
      float             logbeta;
      float             pz[5];
      Rating            rating;
    }                                   inputs;
  struct
    {
      float             dloglambda[4];
      float             dtau[4 * 4];
    }                                   outputs;
};

void
test_grad (void)
{
  GradTestDatum data[] = 
    {{{{0.249783, 0.142865, 0.0519285, 0.223707}, {0.721601, 0.809542,  0.51596, 0.95473, 0.289419, 0.804227, 0.688257, 0.292987,  0.311997, 0.694142, 0.560725, 0.877808, 0.399284, 0.421179,  0.526462, 0.622791},  0.85172, {0.93653, 0.345382, 0.528377, 0.601936, 0.793666}, {1,  2}}, {{0, -10.6682, 0, 0}, {0, 0, 0, 0, -2.63139, -4.57687,  2.67752, 1.3748, 0, 0, 0, 0, 0, 0, 0, 0}}}, {{{0.293454, 0.30467,  0.880336, 0.984124}, {0.777494, 0.34994, 0.590917, 0.179897,  0.089237, 0.0569533, 0.27892, 0.485755, 0.528512, 0.179145,  0.879637, 0.0645764, 0.00205028, 0.556354, 0.0279171, 0.128046},  0.656668, {0.0279769, 0.425981, 0.33438, 0.363214, 0.723306}, {3,  0}}, {{0, 0, 0, -37.7507}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  9.38242, 6.3781, 5.50588, 3.31904}}}, {{{0.545645, 0.350257,  0.585721, 0.373366}, {0.954728, 0.17036, 0.496484, 0.316413,  0.675808, 0.684605, 0.967972, 0.137268, 0.796171, 0.620029,  0.965922, 0.580914, 0.768254, 0.491983, 0.309254, 0.552937},  0.342273, {0.157602, 0.946039, 0.829631, 0.796628, 0.807346}, {3,  4}}, {{0, 0, 0, -20.7623}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, -1.62651, -3.09614, -4.55203, -6.38683}}}, {{{0.360319,  0.456264, 0.8419, 0.636985}, {0.863835, 0.139851, 0.166092,  0.95238, 0.895863, 0.00258338, 0.369921, 0.332351, 0.929941,  0.421669, 0.601667, 0.840368, 0.620688, 0.868732, 0.259394,  0.682766},  0.674648, {0.0391017, 0.462766, 0.875421, 0.31433, 0.582837}, {1,  1}}, {{0, -9.69616, 0, 0}, {0, 0, 0, 0, -1.24552, 5.04463,  2.52432, 1.16242, 0, 0, 0, 0, 0, 0, 0, 0}}}, {{{0.620866,  0.238435, 0.450495, 0.442986}, {0.454774, 0.286055, 0.554632,  0.440403, 0.0848529, 0.953704, 0.624691, 0.0187332, 0.483186,  0.113335, 0.00400311, 0.150001, 0.223791, 0.430569, 0.329355,  0.110899},  0.761025, {0.555149, 0.0150247, 0.528062, 0.140159, 0.316714}, {1,  0}}, {{0, -8.08743, 0, 0}, {0, 0, 0, 0, 2.93964, 1.5313, 1.01838,  0.695561, 0, 0, 0, 0, 0, 0, 0, 0}}}, {{{0.56453, 0.0850758,  0.685384, 0.0306586}, {0.00989742, 0.644673, 0.600531, 0.0769549,  0.385207, 0.62594, 0.117346, 0.96362, 0.381203, 0.475939,  0.893555, 0.53305, 0.0518488, 0.36504, 0.13253, 0.977902},  0.0368241, {0.836978, 0.992371, 0.661188, 0.472295, 0.751902}, {1,  3}}, {{0, -7.60992, 0, 0}, {0, 0, 0,  0, -1.20776, -2.3234, -2.99862, 0.331846, 0, 0, 0, 0, 0, 0, 0,  0}}}, {{{0.306987, 0.63053, 0.462397, 0.107229}, {0.706455,  0.553575, 0.0771905, 0.481289, 0.589109, 0.589955, 0.695987,  0.00534988, 0.695555, 0.0569049, 0.644138, 0.64031, 0.563025,  0.079003, 0.607314, 0.803332},  0.570654, {0.417815, 0.13502, 0.0514294, 0.263668, 0.787285}, {0,  4}}, {{-12.3962, 0, 0, 0}, {-1.1565, -1.39906, -1.64055, -2.98822,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}, {{{0.672623, 0.9442,  0.557213, 0.23371}, {0.595432, 0.462911, 0.968103, 0.643754,  0.899445, 0.457561, 0.272549, 0.58685, 0.255307, 0.817251,  0.709523, 0.507847, 0.647993, 0.0139198, 0.138869, 0.090032},  0.512973, {0.96249, 0.875201, 0.302747, 0.84035, 0.0182902}, {0,  3}}, {{-32.7714, 0, 0, 0}, {-4.71286, -6.51262, -9.27181,  0.0319908, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}, {{{0.317989,  0.0690374, 0.244918, 0.555379}, {0.349886, 0.425283, 0.345473,  0.0978176, 0.0773372, 0.838433, 0.0901665, 0.280566, 0.367814,  0.330587, 0.442174, 0.266646, 0.228945, 0.240555, 0.929201,  0.304156},  0.353743, {0.937807, 0.0888508, 0.285866, 0.0357543, 0.86877}, {3,  3}}, {{0, 0, 0, -15.2205}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, -2.07396, -2.77272, -3.49217, 1.22501}}}, {{{0.843933,  0.730487, 0.685869, 0.443487}, {0.498459, 0.632669, 0.608531,  0.605054, 0.408293, 0.352103, 0.240718, 0.274467, 0.966119,  0.0854566, 0.0117728, 0.0339123, 0.0369178, 0.781301, 0.65803,  0.0961049},  0.948067, {0.495435, 0.622275, 0.227335, 0.104134, 0.764948}, {0,  4}}, {{-59.9879, 0, 0,  0}, {-4.82581, -7.64861, -8.49722, -11.6977, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0}}}}; 

  for (unsigned int i = 0; i < sizeof (data) / sizeof (data[0]); ++i)
    {
      std::vector<float> tau (data[i].inputs.tau, data[i].inputs.tau + 4 * 4);
      std::vector<float> loglambda (data[i].inputs.loglambda,
                                    data[i].inputs.loglambda + 4);
      std::vector<float> gamma (4, 0);
      std::vector<float> chi (4, 0);
      Ordinal ordinal (&tau[0], &gamma[0], &loglambda[0], &chi[0], 1, 4, 5, 0);

      std::vector<float> pz (data[i].inputs.pz, data[i].inputs.pz + 5);

      Ordinal::DFunc dfunc (data[i].inputs.rating,
                            pz,
                            exp (data[i].inputs.logbeta),
                            data[i].inputs.tau + data[i].inputs.rating.worker * (5 - 1),
                            data[i].inputs.loglambda[data[i].inputs.rating.worker],
                            5);

      PianoVec s0 (5, 0);
      PianoVec sprime0 (dfunc (0, s0));

      assert (fabs (sprime0[0] - data[i].outputs.dloglambda[data[i].inputs.rating.worker]) <=
              1e-4 * (1 + fabs (sprime0[0]) + fabs (data[i].outputs.dloglambda[data[i].inputs.rating.worker])) ||
              (std::cerr << sprime0[0] << " ?= " << data[i].outputs.dloglambda[data[i].inputs.rating.worker] << std::endl,
               0));
      
      for (unsigned int j = 0; j < 4; ++j)
        {
          assert (fabs (sprime0[j+1] - data[i].outputs.dtau[data[i].inputs.rating.worker * (5 - 1) + j]) <=
                  1e-4 * (1 + fabs (sprime0[j+1]) + fabs (data[i].outputs.dtau[data[i].inputs.rating.worker * (5 - 1) + j])) ||
                  (std::cerr << "tau[" << j << "] = " << sprime0[j+1] << " ?= " << data[i].outputs.dtau[data[i].inputs.rating.worker * (5 - 1) + j] << std::endl,
                   0));
        }
    }
}

struct UpdateTestDatum 
{ 
  struct
    {
      float             loglambda[4];
      float             tau[4 * 4];
      float             logbeta;
      float             pz[5];
      Rating            rating;
    }                                   inputs;
  struct
    {
      float             newloglambda[4];
      float             newtau[4 * 4];
    }                                   outputs;
};

void
test_update (void)
{
  UpdateTestDatum data[] = 
    {{{{0.481526, 0.602248, 0.212238, 0.367294}, {0.282237, 0.542896,  0.400868, 0.31699, 0.492443, 0.970214, 0.667134, 0.541454,  0.363911, 0.701567, 0.608665, 0.0960661, 0.429604, 0.50927,  0.93115, 0.531818},  0.234049, {0.915507, 0.696302, 0.767295, 0.752523, 0.313259}, {2,  2}}, {{0.481526, 0.602248, -0.45528, 0.367294}, {0.282237,  0.542896, 0.400868, 0.31699, 0.492443, 0.970214, 0.667134,  0.541454, -0.274576, -0.698027, 1.23186, 0.33375, 0.429604,  0.50927, 0.93115, 0.531818}}}, {{{0.484064, 0.400001, 0.470286,  0.770364}, {0.0831962, 0.0830105, 0.977842, 0.80015, 0.416062,  0.541557, 0.613932, 0.0985826, 0.807396, 0.44549, 0.184328,  0.589312, 0.876247, 0.913673, 0.950279, 0.673805},  0.179945, {0.146378, 0.197755, 0.360546, 0.695881, 0.746377}, {0,  3}}, {{-0.279721, 0.400001, 0.470286,  0.770364}, {-0.0842397, -0.379612, -0.19148, 1.13246, 0.416062,  0.541557, 0.613932, 0.0985826, 0.807396, 0.44549, 0.184328,  0.589312, 0.876247, 0.913673, 0.950279, 0.673805}}}, {{{0.72747,  0.590182, 0.612685, 0.663367}, {0.749627, 0.790032, 0.196623,  0.12181, 0.135696, 0.691449, 0.389226, 0.67632, 0.951368,  0.102137, 0.51298, 0.762647, 0.00108913, 0.428332, 0.333035,  0.616269},  0.803334, {0.0677865, 0.637154, 0.869892, 0.0758639, 0.477605}, {1,  2}}, {{0.72747, -0.032931, 0.612685, 0.663367}, {0.749627,  0.790032, 0.196623, 0.12181, -0.20093, -0.827077, 1.3785,  0.963452, 0.951368, 0.102137, 0.51298, 0.762647, 0.00108913,  0.428332, 0.333035, 0.616269}}}, {{{0.0244696, 0.206525, 0.326236,  0.687573}, {0.827847, 0.0847152, 0.190541, 0.996124, 0.43862,  0.408396, 0.239173, 0.893987, 0.925641, 0.645749, 0.238084,  0.465655, 0.592606, 0.0294795, 0.43475, 0.397868},  0.955451, {0.159588, 0.358886, 0.920263, 0.930982, 0.953062}, {3,  1}}, {{0.0244696, 0.206525, 0.326236, -0.678868}, {0.827847,  0.0847152, 0.190541, 0.996124, 0.43862, 0.408396, 0.239173,  0.893987, 0.925641, 0.645749, 0.238084, 0.465655, 0.0485838,  2.32586, 1.62826, 0.847141}}}, {{{0.0326497, 0.23269, 0.103135,  0.868347}, {0.842109, 0.236566, 0.664515, 0.459951, 0.602936,  0.34258, 0.738874, 0.814203, 0.364853, 0.876925, 0.146268,  0.784723, 0.930102, 0.479057, 0.190817, 0.625136},  0.571216, {0.558794, 0.259835, 0.672074, 0.538567, 0.326103}, {1,  3}}, {{0.0326497, -1.4776, 0.103135, 0.868347}, {0.842109,  0.236566, 0.664515, 0.459951, 0.258906, -0.325293, -0.335788,  0.961705, 0.364853, 0.876925, 0.146268, 0.784723, 0.930102,  0.479057, 0.190817, 0.625136}}}, {{{0.1567, 0.803727, 0.696457,  0.0895369}, {0.492185, 0.343775, 0.0935212, 0.746957, 0.753311,  0.529572, 0.728669, 0.870032, 0.607043, 0.744849, 0.798566,  0.390975, 0.416226, 0.119714, 0.22735, 0.832182},  0.156391, {0.44764, 0.688783, 0.506079, 0.999691, 0.643913}, {1,  0}}, {{0.1567, -0.774898, 0.696457, 0.0895369}, {0.492185,  0.343775, 0.0935212, 0.746957, 2.32993, 1.58402, 1.28312, 1.04483,  0.607043, 0.744849, 0.798566, 0.390975, 0.416226, 0.119714,  0.22735, 0.832182}}}, {{{0.992326, 0.416542, 0.507506,  0.300138}, {0.898805, 0.669584, 0.754195, 0.770566, 0.170136,  0.799552, 0.147152, 0.0257166, 0.37157, 0.408577, 0.730926,  0.906003, 0.14422, 0.576395, 0.574535, 0.458363},  0.455437, {0.0703163, 0.574845, 0.814449, 0.463111, 0.653775}, {0,  0}}, {{-0.815744, 0.416542, 0.507506, 0.300138}, {2.56683, 1.7465,  1.34121, 0.963868, 0.170136, 0.799552, 0.147152, 0.0257166,  0.37157, 0.408577, 0.730926, 0.906003, 0.14422, 0.576395,  0.574535, 0.458363}}}, {{{0.067339, 0.514311, 0.564306,  0.98419}, {0.313144, 0.743745, 0.39417, 0.184638, 0.165992,  0.718029, 0.0226002, 0.776061, 0.435066, 0.812026, 0.87838,  0.199667, 0.860531, 0.353663, 0.422944, 0.12935},  0.285686, {0.539213, 0.959833, 0.475576, 0.218347, 0.024902}, {1,  0}}, {{0.067339, -0.0176816, 0.564306, 0.98419}, {0.313144,  0.743745, 0.39417, 0.184638, 1.62847, 1.27806, 0.286879, 0.813345,  0.435066, 0.812026, 0.87838, 0.199667, 0.860531, 0.353663,  0.422944, 0.12935}}}}; 

  for (unsigned int i = 0; i < sizeof (data) / sizeof (data[0]); ++i)
    {
      std::vector<float> tau (data[i].inputs.tau, data[i].inputs.tau + 4 * 4);
      std::vector<float> loglambda (data[i].inputs.loglambda,
                                    data[i].inputs.loglambda + 4);
      std::vector<float> gamma (4, 0);
      std::vector<float> chi (4, 0);
      Ordinal ordinal (&tau[0], &gamma[0], &loglambda[0], &chi[0], 1, 4, 5, 0);

      std::vector<float> pz (data[i].inputs.pz, data[i].inputs.pz + 5);

      ordinal.update (1,
                      1,
                      0,
                      exp (data[i].inputs.logbeta),
                      pz,
                      data[i].inputs.rating);

      assert (ordinal.failures () == 0);

      for (unsigned int j = 0; j < 4; ++j)
        {
          assert (fabs (loglambda[j] - data[i].outputs.newloglambda[j]) <=
                  1e-3 * (1 + fabs (loglambda[j]) + fabs (data[i].outputs.newloglambda[j])) ||
                  (std::cerr << loglambda[j] << " ?= " << data[i].outputs.newloglambda[j] << std::endl,
                   0));
        }

      for (unsigned int j = 0; j < 4 * 4; ++j)
        {
          assert (fabs (tau[j] - data[i].outputs.newtau[j]) <=
                  1e-3 * (1 + fabs (tau[j]) + fabs (data[i].outputs.newtau[j])) ||
                  (std::cerr << "tau[" << j << "] = " << tau[j] << " ?= " << data[i].outputs.newtau[j] << std::endl,
                   0));
        }
    }
}

}

int 
main (void) 
{
  srand48 (69);

  test_logprating ();
  test_grad ();
  test_update ();

  return 0;
}
